package com.vmj.instructions.references;

import com.vmj.heap.VMClass;
import com.vmj.heap.RuntimeConstantPool;
import com.vmj.heap.Method;
import com.vmj.heap.MethodRef;
import com.vmj.heap.VMObject;
import com.vmj.instructions.base.Index16Instruction;
import com.vmj.instructions.base.MethodInvokeLogic;
import com.vmj.rtda.Frame;

/**
 * 用于父类方法调用
 *
 * Created by yangshoujun on 2017/8/27 下午3:23.
 */
public class InvokeSpecial extends Index16Instruction {

    @Override
    public void execute(Frame frame) {
        VMClass currentCls = frame.getMethod().getClazz();
        RuntimeConstantPool cp = currentCls.getRuntimeConstantPool();
        MethodRef methodRef = (MethodRef) cp.getConstant(this.getIndex()).getValue();
        VMClass resolvedCls = methodRef.resolvedClass();
        Method resolvedMethod = methodRef.resolvedMethod();
        if("<init>".equals(resolvedMethod.getName()) && !resolvedCls.equals(resolvedMethod.getClazz())) {
            throw new RuntimeException("java.lang.NoSuchMethodException");
        }

        if(resolvedMethod.isStatic()) {
            throw new RuntimeException("java.lang.IncompatibalClassChangeError");
        }

        VMObject thisRef = frame.getOperandStack().getRefFromTop(resolvedMethod.getArgSlotCount() - 1);
        if(thisRef == null) {
            throw new RuntimeException("java.lang.NullPointerException");
        }

        if(resolvedMethod.isProtected()
                && resolvedMethod.getClazz().isSubClassOf(currentCls)
                && !resolvedMethod.getClazz().getPackageName().equals(currentCls.getPackageName())
                && !thisRef.getClazz().equals(currentCls)
                && !thisRef.getClazz().isSubClassOf(currentCls)) {
            throw new RuntimeException("java.lang.IllegalAccessError");
        }

        Method methodToBeInvoked = resolvedMethod;
        if(currentCls.isSuper()
                && resolvedCls.isSubClassOf(currentCls)
                && !"<init>".equals(resolvedMethod.getName())) {
            // 从父类中查找要调用的方法
            methodToBeInvoked = MethodRef.lookupMethodInClass(currentCls.getSuperClass(), methodRef.getName(), methodRef.getDescriptor());
        }

        if(methodToBeInvoked == null || methodToBeInvoked.isAbstract()) {
            throw new RuntimeException("java.lang.AbstractMethodError");
        }
        MethodInvokeLogic.invokeMethod(frame, methodToBeInvoked);
    }
}
